﻿using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

namespace ExpressionToCodeLib {
    static class ReflectionHelpers {
        public static PropertyInfo GetPropertyIfGetter(MethodInfo mi) {
            bool supposedGetter = mi.Name.StartsWith("get_");
            //bool supposedSetter = mi.Name.StartsWith("set_");

            if (!mi.IsSpecialName || !supposedGetter) {
                return null;
            }
            var pName = mi.Name.Substring(4);
            const BindingFlags bindingFlags =
                BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
            var pars = mi.GetParameters();
            if (pars.Length == 0) {
                return mi.DeclaringType.GetProperty(pName, bindingFlags);
            } else {
                return mi.DeclaringType.GetProperty(
                    pName,
                    bindingFlags,
                    null,
                    mi.ReturnType,
                    pars.Select(p => p.ParameterType).ToArray(),
                    null);
            }
        }

        public static bool IsMemberInfoStatic(MemberInfo mi) {
            if (mi is FieldInfo) {
                return ((FieldInfo)mi).IsStatic;
            } else if (mi is MethodInfo) {
                return (((MethodInfo)mi).Attributes & MethodAttributes.Static) == MethodAttributes.Static;
            } else if (mi is PropertyInfo) {
                PropertyInfo pi = (PropertyInfo)mi;
                return pi.CanRead ? pi.GetGetMethod().IsStatic : pi.GetSetMethod().IsStatic;
            } else if (mi.MemberType == MemberTypes.NestedType) {
                return true;
            } else if (mi is EventInfo) {
                return ((EventInfo)mi).GetAddMethod(true).IsStatic;
            } else {
                throw new ArgumentOutOfRangeException(
                    "mi",
                    "Expression represents a member access for member" + mi.Name + " of membertype " + mi.MemberType
                        + " that is unsupported");
            }
        }

        public static bool HasBuiltinConversion(Type from, Type to) {
            return
                from == typeof(sbyte)
                    && (to == typeof(short) || to == typeof(int) || to == typeof(long) || to == typeof(float)
                        || to == typeof(double) || to == typeof(decimal))
                    || from == typeof(byte)
                        && (to == typeof(short) || to == typeof(ushort) || to == typeof(int) || to == typeof(uint)
                            || to == typeof(long) || to == typeof(ulong) || to == typeof(float) || to == typeof(double)
                            || to == typeof(decimal))
                    || from == typeof(short)
                        && (to == typeof(int) || to == typeof(long) || to == typeof(float) || to == typeof(double)
                            || to == typeof(decimal))
                    || from == typeof(ushort)
                        && (to == typeof(int) || to == typeof(uint) || to == typeof(long) || to == typeof(ulong)
                            || to == typeof(float) || to == typeof(double) || to == typeof(decimal))
                    || from == typeof(int)
                        && (to == typeof(long) || to == typeof(float) || to == typeof(double) || to == typeof(decimal))
                    || from == typeof(uint)
                        && (to == typeof(long) || to == typeof(ulong) || to == typeof(float) || to == typeof(double)
                            || to == typeof(decimal))
                    || from == typeof(long) && (to == typeof(float) || to == typeof(double) || to == typeof(decimal))
                    || from == typeof(char)
                        && (to == typeof(ushort) || to == typeof(int) || to == typeof(uint) || to == typeof(long)
                            || to == typeof(ulong) || to == typeof(float) || to == typeof(double) || to == typeof(decimal))
                    || from == typeof(float) && (to == typeof(double))
                    || from == typeof(ulong) && (to == typeof(float) || to == typeof(double) || to == typeof(decimal))
                ;
        }

        public static bool CanImplicitlyCast(Type from, Type to) {
            return to.IsAssignableFrom(from) || HasBuiltinConversion(from, to);

            //TODO: extend with op_Implicit support.
            //Use this to test if a conversion is required.
            //That means expressions checking if a child is a conversion, 
            //and if so checking if the conversion can be implicit,
            //and if so checking if the code compiles without conversion
            //if it does NOT compile, and IS implicit, then we can omit it since the compiler will add it.
        }

        public enum TypeClass {
            BuiltinType,
            AnonymousType,
            ClosureType,
            StructType,
            NormalType,
        }

        public static TypeClass GuessTypeClass(this Type type) {
            bool compilerGenerated = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any();
            string name = type.Name;
            bool name_StartWithLessThan = name.StartsWith("<");
            bool isBuiltin = type.IsPrimitive || type.IsEnum || type == typeof(decimal) || type == typeof(string)
                || typeof(Type).IsAssignableFrom(type);

            if (name_StartWithLessThan && compilerGenerated) {
                bool named_AnonymousType = name.Contains("AnonymousType");
                bool named_DisplayClass = name.Contains("DisplayClass");
                bool isGeneric = type.IsGenericType;
                bool isNested = type.IsNested;

                if (!isBuiltin && isGeneric && !isNested && named_AnonymousType) {
                    return TypeClass.AnonymousType;
                } else if (!isBuiltin && isNested && named_DisplayClass) {
                    return TypeClass.ClosureType;
                }
                    //note that since genericness+nestedness don't overlap, these typeclasses aren't confusable.
                else {
                    throw new ArgumentException(
                        "Can't deal with unknown-style compiler generated class " + type.FullName + " " + named_AnonymousType + ", " + named_DisplayClass + ", " + isGeneric
                            + ", " + isNested);
                }
            } else if (!compilerGenerated && !name_StartWithLessThan) {
                return isBuiltin ? TypeClass.BuiltinType : type.IsValueType ? TypeClass.StructType : TypeClass.NormalType;
            } else {
                throw new ArgumentException("Unusual type, heuristics uncertain:" + name);
            }
        }
    }
}
